<?php
/**
 * @copyright   Copyright(c) 2018
 * @author      tianjie
 * @link        http://www.tinphp.com/
 */

namespace tian;

use tian\exception\HttpException;

class Route
{
    // 路由规则
    private static $rules = [
        'get' => [],
        'post' => [],
        'put' => [],
        'delete' => [],
        'patch' => [],
        'head' => [],
        'options' => [],
        '*' => [],
        'alias' => [],
        'domain' => [],
        'pattern' => [],
        'name' => [],
    ];

    private static $domainRule;

    public static function import(array $rule, $type = '*')
    {
        // 检查变量规则
        if (isset($rule['__pattern__'])) {
            self::pattern($rule['__pattern__']);
            unset($rule['__pattern__']);
        }
    }

    /**
     * 注册路由
     * @access public
     * @param string|array $rule 路由规则
     * @param string $route 路由地址
     * @return void
     */
    public static function any($rule, $route = '')
    {
        self::rule($rule, $route, '*');
    }

    /**
     * 注册GET路由
     * @access public
     * @param string|array $rule 路由规则
     * @param string $route 路由地址
     * @return void
     */
    public static function get($rule, $route = '')
    {
        self::rule($rule, $route, 'GET');
    }

    /**
     * 注册POST路由
     * @access public
     * @param string|array $rule 路由规则
     * @param string $route 路由地址
     * @return void
     */
    public static function post($rule, $route = '')
    {
        self::rule($rule, $route, 'POST');
    }

    /**
     * 注册PUT路由
     * @access public
     * @param string|array $rule 路由规则
     * @param string $route 路由地址
     * @return void
     */
    public static function put($rule, $route = '')
    {
        self::rule($rule, $route, 'PUT');
    }

    /**
     * 注册DELETE路由
     * @access public
     * @param string|array $rule 路由规则
     * @param string $route 路由地址
     * @return void
     */
    public static function delete($rule, $route = '')
    {
        self::rule($rule, $route, 'DELETE');
    }

    /**
     * 注册路由规则
     * @access public
     * @param string|array $rule 路由规则
     * @param string $route 路由地址
     * @param string $type 请求类型
     * @return void
     */
    public static function rule($rule, $route = '', $type = '*')
    {
        $type = strtolower($type);
        if (is_array($rule) && empty($route)) {
            foreach ($rule as $key => $val) {
                self::setRule($key, $val, $type);
            }
        } else {
            self::setRule($rule, $route, $type);
        }
    }

    /**
     * 注册变量规则
     * @access public
     * @param string|array $name 变量名
     * @param string $rule 变量规则
     * @return void
     */
    public static function pattern($name = null, $rule = '')
    {
        if (is_array($name)) {
            self::$rules['pattern'] = array_merge(self::$rules['pattern'], $name);
        } else {
            self::$rules['pattern'][$name] = $rule;
        }
    }

    /**
     * 设置路由规则
     * @access public
     * @param string|array $rule 路由规则
     * @param string $route 路由地址
     * @param string $type 请求类型
     * @return void
     */
    protected static function setRule($rule, $route, $type = '*')
    {
        if ('/' != $rule) {
            $rule = trim($rule, '/');
        }
        $vars = self::parseVar($rule);

        if (isset($rule)) {
            self::name($rule, [$rule, $vars]);
        }

        if ('*' != $type && isset(self::$rules['*'][$rule])) {
            unset(self::$rules['*'][$rule]);
        }

        self::$rules[$type][$rule] = ['rule' => $rule, 'route' => $route, 'var' => $vars];

        if ('*' == $type) {
            // 注册路由快捷方式
            foreach (['get', 'post', 'put', 'delete', 'patch', 'head', 'options'] as $method) {
                self::$rules[$method][$rule] = true;
            }
        }
    }

    /**
     * 设置或者获取路由标识
     * @access public
     * @param string|array $name 路由命名标识 数组表示批量设置
     * @param array $value 路由地址及变量信息
     * @return array
     */
    public static function name($name = '', $value = null)
    {
        if (!is_null($value)) {
            self::$rules['name'][strtolower($name)][] = $value;
        } else {
            $name = strtolower($name);
            return self::$rules['name'][$name] ?? null;
        }
    }

    /**
     * 分析路由规则中的变量
     * @access public
     * @param string $rule 路由规则
     * @return array
     */
    private static function parseVar($rule)
    {
        // 提取路由规则中的变量
        $var = [];
        foreach (explode('/', $rule) as $val) {
            $optional = false;
            if (false !== strpos($val, '<') && preg_match_all('/<(\w+(\??))>/', $val, $matches)) {
                foreach ($matches[1] as $name) {
                    if (strpos($name, '?')) {
                        $name = substr($name, 0, -1);
                        $optional = true;
                    } else {
                        $optional = false;
                    }
                    $var[$name] = $optional ? 2 : 1;
                }
            }

            if (0 === strpos($val, '[:')) {
                // 可选参数
                $optional = true;
                $val = substr($val, 1, -1);
            }
            if (0 === strpos($val, ':')) {
                // URL变量
                $name = substr($val, 1);
                $var[$name] = $optional ? 2 : 1;
            }
        }
        return $var;
    }

    // 路由检测
    public static function check($request, $url, $depr = '/')
    {
        // 分隔符替换 确保路由定义使用统一的分隔符
        $url = str_replace($depr, '|', $url);

        $method = strtolower($request->method());
        //获取当前请求类型的路由规则
        $rules = self::$rules[$method] ?? [];

        if ('|' != $url) {
            $url = rtrim($url, '|');
        }
        $item = str_replace('|', '/', $url);

        // 静态路由规则检测
        if (isset($rules[$item])) {
            $rule = $rules[$item];
            if (true === $rule) {
                $rule = self::getRouteExpress($item);
            }
            if (!empty($rule['route'])) {
                return self::parseRule($item, $rule['route'], $url);
            }
        }

        // 路由规则检测
        if (!empty($rules)) {
            return self::checkRoute($request, $rules, $url, $depr);
        }
        return false;
    }

    private static function getRouteExpress($key)
    {
        return self::$domainRule ? self::$domainRule['*'][$key] : self::$rules['*'][$key];
    }

    /**
     * 检测路由规则
     * @access private
     * @param Request $request
     * @param array $rules 路由规则
     * @param string $url URL地址
     * @param string $depr URL分割符
     * @return mixed
     */
    private static function checkRoute($request, $rules, $url, $depr = '/')
    {
        foreach ($rules as $key => $item) {
            if (true === $item) {
                $item = self::getRouteExpress($key);
            }
            if (!isset($item['rule'])) {
                continue;
            }
            $rule = $item['rule'];
            $route = $item['route'];

            $result = self::checkRule($rule, $route, $url, [], [], $depr);
            if (false !== $result) {
                return $result;
            }
        }
        return false;
    }

    /**
     * 解析规则路由
     * @access private
     * @param string $rule 路由规则
     * @param string $route 路由地址
     * @param string $pathinfo URL地址
     * @return array
     */
    private static function parseRule($rule, $route, $pathinfo, $option = [], $matches = [])
    {
        $request = Request::instance();

        if ($rule) {
            $rule = explode('/', $rule);
            // 获取URL地址中的参数
            $paths = explode('|', $pathinfo);
            foreach ($rule as $item) {
                $fun = '';
                if (0 === strpos($item, '[:')) {
                    $item = substr($item, 1, -1);
                }
                if (0 === strpos($item, ':')) {
                    $var = substr($item, 1);
                    $matches[$var] = array_shift($paths);
                } else {
                    // 过滤URL中的静态变量
                    array_shift($paths);
                }
            }
        } else {
            $paths = explode('|', $pathinfo);
        }

        // 替换路由地址中的变量
        if (is_string($route) && !empty($matches)) {
            foreach ($matches as $key => $val) {
                if (false !== strpos($route, ':' . $key)) {
                    $route = str_replace(':' . $key, $val, $route);
                }
            }
        }

        // 解析额外参数
        self::parseUrlParams(empty($paths) ? '' : implode('|', $paths), $matches);
        // 记录匹配的路由信息
        $request->routeInfo(['rule' => $rule, 'route' => $route, 'option' => $option, 'var' => $matches]);

        //根据请求类型将参数绑定到对应参数容器
        $method = $request->method();
        $request->$method($matches);

        // 路由到模块/控制器/操作
        return self::parseModule($route, isset($option['convert']) ? $option['convert'] : false);
    }

    /**
     * 解析URL地址为 模块/控制器/操作
     * @access private
     * @param string $url     URL地址
     * @param bool   $convert 是否自动转换URL地址
     * @return array
     */
    private static function parseModule($url, $convert = false)
    {
        list($path, $var) = self::parseUrlPath($url);
        $action           = array_pop($path);
        $controller       = !empty($path) ? array_pop($path) : null;
        $module           = Config::get('app_multi_module') && !empty($path) ? array_pop($path) : null;

        // 设置当前请求的路由变量
        Request::instance()->route($var);
        // 路由到模块/控制器/操作
        return ['type' => 'module', 'module' => [$module, $controller, $action], 'convert' => $convert];
    }

    /**
     * 解析URL的pathinfo参数和变量
     * @access private
     * @param string $url URL地址
     * @return array
     */
    private static function parseUrlPath($url)
    {
        // 分隔符替换 确保路由定义使用统一的分隔符
        $url = str_replace('|', '/', $url);
        $url = trim($url, '/');
        $var = [];
        if (false !== strpos($url, '?')) {
            // [模块/控制器/操作?]参数1=值1&参数2=值2...
            $info = parse_url($url);
            $path = explode('/', $info['path']);
            parse_str($info['query'], $var);
        } elseif (strpos($url, '/')) {
            // [模块/控制器/操作]
            $path = explode('/', $url);
        } else {
            $path = [$url];
        }
        return [$path, $var];
    }

    /**
     * 检测路由规则
     * @access private
     * @param string $rule 路由规则
     * @param string $route 路由地址
     * @param string $url URL地址
     * @param array $pattern 变量规则
     * @param array $option 路由参数
     * @param string $depr URL分隔符（全局）
     * @return array|false
     */
    private static function checkRule($rule, $route, $url, $pattern, $option, $depr)
    {
        // 检查完整规则定义
        if (isset($pattern['__url__']) && !preg_match(0 === strpos($pattern['__url__'], '/') ? $pattern['__url__'] : '/^' . $pattern['__url__'] . '/', str_replace('|', $depr, $url))) {
            return false;
        }
        // 检查路由的参数分隔符
        if (isset($option['param_depr'])) {
            $url = str_replace(['|', $option['param_depr']], [$depr, '|'], $url);
        }

        $len1 = substr_count($url, '|');
        $len2 = substr_count($rule, '/');
        // 多余参数是否合并
        $merge = !empty($option['merge_extra_vars']);
        if ($merge && $len1 > $len2) {
            $url = str_replace('|', $depr, $url);
            $url = implode('|', explode($depr, $url, $len2 + 1));
        }

        if ($len1 >= $len2 || strpos($rule, '[')) {
            if (!empty($option['complete_match'])) {
                // 完整匹配
                if (!$merge && $len1 != $len2 && (false === strpos($rule, '[') || $len1 > $len2 || $len1 < $len2 - substr_count($rule, '['))) {
                    return false;
                }
            }
            $pattern = array_merge(self::$rules['pattern'], $pattern);
            if (false !== $match = self::match($url, $rule, $pattern)) {
                // 匹配到路由规则
                return self::parseRule($rule, $route, $url, $option, $match);
            }
        }
        return false;
    }

    /**
     * 检测URL和规则路由是否匹配
     * @access private
     * @param string $url URL地址
     * @param string $rule 路由规则
     * @param array $pattern 变量规则
     * @return array|false
     */
    private static function match($url, $rule, $pattern)
    {
        $m2 = explode('/', $rule);
        $m1 = explode('|', $url);

        $var = [];
        foreach ($m2 as $key => $val) {
            // val中定义了多个变量 <id><name>
            if (false !== strpos($val, '<') && preg_match_all('/<(\w+(\??))>/', $val, $matches)) {
                $value = [];
                $replace = [];
                foreach ($matches[1] as $name) {
                    if (strpos($name, '?')) {
                        $name = substr($name, 0, -1);
                        $replace[] = '(' . (isset($pattern[$name]) ? $pattern[$name] : '\w+') . ')?';
                    } else {
                        $replace[] = '(' . (isset($pattern[$name]) ? $pattern[$name] : '\w+') . ')';
                    }
                    $value[] = $name;
                }
                $val = str_replace($matches[0], $replace, $val);
                if (preg_match('/^' . $val . '$/', isset($m1[$key]) ? $m1[$key] : '', $match)) {
                    array_shift($match);
                    foreach ($value as $k => $name) {
                        if (isset($match[$k])) {
                            $var[$name] = $match[$k];
                        }
                    }
                    continue;
                } else {
                    return false;
                }
            }

            if (0 === strpos($val, '[:')) {
                // 可选参数
                $val = substr($val, 1, -1);
                $optional = true;
            } else {
                $optional = false;
            }
            if (0 === strpos($val, ':')) {
                // URL变量
                $name = substr($val, 1);
                if (!$optional && !isset($m1[$key])) {
                    return false;
                }
                if (isset($m1[$key]) && isset($pattern[$name])) {
                    // 检查变量规则
                    if ($pattern[$name] instanceof \Closure) {
                        $result = call_user_func_array($pattern[$name], [$m1[$key]]);
                        if (false === $result) {
                            return false;
                        }
                    } elseif (!preg_match(0 === strpos($pattern[$name], '/') ? $pattern[$name] : '/^' . $pattern[$name] . '$/', $m1[$key])) {
                        return false;
                    }
                }
                $var[$name] = isset($m1[$key]) ? $m1[$key] : '';
            } elseif (!isset($m1[$key]) || 0 !== strcasecmp($val, $m1[$key])) {
                return false;
            }
        }
        // 成功匹配后返回URL中的动态变量数组
        return $var;
    }

    /**
     * 解析模块的URL地址 [模块/控制器/操作?]参数1=值1&参数2=值2...
     * @access public
     * @param string $url        URL地址
     * @param string $depr       URL分隔符
     * @param bool   $autoSearch 是否自动深度搜索控制器
     * @return array
     */
    public static function parseUrl($url, $depr = '/', $autoSearch = false)
    {
        $url              = str_replace($depr, '|', $url);
        list($path, $var) = self::parseUrlPath($url);
        $route            = [null, null, null];
        if (isset($path)) {
            // 解析模块
            $module = Config::get('app_multi_module') ? array_shift($path) : null;
            if ($autoSearch) {
                // 自动搜索控制器
                $dir    = APP_PATH . ($module ? $module . '/' : '') . 'controller';
                $item   = [];
                $find   = false;
                foreach ($path as $val) {
                    $item[] = $val;
                    $file   = $dir . '/' . str_replace('.', '/', $val) . SUFFIX;
                    $file   = pathinfo($file, PATHINFO_DIRNAME) . '/' . self::parseName(pathinfo($file, PATHINFO_FILENAME), 1) . SUFFIX;
                    if (is_file($file)) {
                        $find = true;
                        break;
                    } else {
                        $dir .= '/' . self::parseName($val);
                    }
                }
                if ($find) {
                    $controller = implode('.', $item);
                    $path       = array_slice($path, count($item));
                } else {
                    $controller = array_shift($path);
                }
            } else {
                // 解析控制器
                $controller = !empty($path) ? array_shift($path) : null;
            }
            // 解析操作
            $action = !empty($path) ? array_shift($path) : null;
            // 解析额外参数
            self::parseUrlParams(empty($path) ? '' : implode('|', $path));
            // 封装路由
            $route = [$module, $controller, $action];
            // 检查地址是否被定义过路由
            $name  = strtolower($module . '/' . self::parseName($controller, 1) . '/' . $action);
            $name2 = '';
            if (empty($module) || isset($bind) && $module == $bind) {
                $name2 = strtolower(self::parseName($controller, 1) . '/' . $action);
            }

            if (isset(self::$rules['name'][$name]) || isset(self::$rules['name'][$name2])) {
                throw new HttpException(404, 'invalid request:' . str_replace('|', $depr, $url));
            }
        }
        return ['type' => 'module', 'module' => $route];
    }

    /**
     * 解析URL地址中的参数Request对象
     * @access private
     * @param string $url 路由规则
     * @param array  $var 变量
     * @return void
     */
    private static function parseUrlParams($url, &$var = [])
    {
        if ($url) {
            if (Config::get('url_param_type')) {
                $var += explode('|', $url);
            } else {
                preg_replace_callback('/(\w+)\|([^\|]+)/', function ($match) use (&$var) {
                    $var[$match[1]] = strip_tags($match[2]);
                }, $url);
            }
        }
        // 设置当前请求的参数
        Request::instance()->route($var);
    }

    /**
     * 字符串命名风格转换
     * @access public
     * @param string|null $name 字符串
     * @param integer $type 转换类型  0将Java风格转换为C的风格，1将C风格转换为Java的风格
     * @param bool $ucfirst 首字母是否大写（驼峰规则）
     * @return string
     */
    private static function parseName(?string $name, int $type = 0, bool $ucfirst = true): string
    {
        if ($type) {
            $name = preg_replace_callback('/_([a-zA-Z])/', function ($match) {
                return strtoupper($match[1]);
            }, $name);

            return $ucfirst ? ucfirst($name) : lcfirst($name);
        }
        return strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_"));
    }

    //获取标识序列
    public static function getRouteName()
    {
        return self::$rules['name'];
    }
}
